<?php

/**
* Google Checkout Component
* @author WalkerHamilton
* @license MIT
* @version 0.1
*/

/**
* Download the php docs from Google: http://code.google.com/apis/checkout/samplecode.html
* Drop CheckoutAPIFunctions.php, GlobalAPIFunctions.php, MerchantCalculationsAPIFunctions.php,
* OrderProcessingAPIFunctions.php, and ResponseHandlerAPIFunctions.php into a folder named "gcheckout"
* in your vendors folder
*
*	Add these Configuration options to your core.php file.
*	Configure::write('Checkout.id', 'merchant ID here');
*	Configure::write('Checkout.key', 'merchant key here');
*	Configure::write('Checkout.debug', true); //set to false to run on live checkout server
*	Configure::write('Checkout.continue_shopping', 'your continue shopping URL here');
*	Configure::write('Checkout.currency', 'currency here'); //Use the alphabetic code from this table: http://www.iso.org/iso/support/faqs/faqs_widely_used_standards/widely_used_standards_other/currency_codes/currency_codes_list-1.htm
*/


/* No Shipping, Ship From, or Taxes have been implemented */

class GoogleCheckoutComponent extends Object
{
	var $components = array('Session');

	var $_merchantID;
	var $_merchantKey;
	//1 == log to errorlog only, 2 == log to browser only, 3 == log to browser & error log
	var $_loggingLevel = 1;
	var $_currency;
	var $_testing;
	var $_approotdir = APP_DIR;
	var $_items = array();
	var $_gserve;
	var $_continueShopping;

	/*
	 * 
	 */
	function startup(&$controller)
	{
		//Is there a better way to include these depending on what's going on within the application?
		//For instance, if we're in shopping more, then we don't need request & response files
		//If we're in post-shopping mode, we don't need cart, item, shipping or tax (in all likelihood)
		vendor('gcheckout'.DS.'googlecart');
		vendor('gcheckout'.DS.'googleitem');
		vendor('gcheckout'.DS.'googleshipping');
		vendor('gcheckout'.DS.'googletax');
		vendor('gcheckout'.DS.'googlerequest');
		vendor('gcheckout'.DS.'googleresponse');

		/* We always need these */
		vendor('gcheckout'.DS.'googlemerchantcalculations');
		vendor('gcheckout'.DS.'googleresult');
		vendor('gcheckout'.DS.'googlelog');
		
/*
		$this->_merchantID = (isset($controller->merchantID)) ? $controller->merchantID : '';
		$this->_merchantKey = (isset($controller->merchantKey)) ? $controller->merchantKey : '';
		$this->_currency = (isset($controller->currency)) ? $controller->currency : 'USD';
		$this->_continueShopping = (isset($controller->continueShopping)) ? $controller->continueShopping : 'http://example.org';
		$this->_checkoutTesting = ($controller->checkoutTesting) ? true : false;

*/
		$this->_merchantID = Configure::read('Checkout.id');
		$this->_merchantKey = Configure::read('Checkout.key');
		$this->_currency = Configure::read('Checkout.currency');
		$this->_continueShopping = Configure::read('Checkout.continue_shopping');
		$this->_checkoutTesting = Configure::read('Checkout.debug');

		$controller->set('checkout_config', Configure::read('Checkout'));

		$this->_restoreFromSessionArr();

		$this->_gserve = ($this->_checkoutTesting) ? 'sandbox' : 'Production';
	}


	/*
	 * 
	 */
	function setItem($input) {
		foreach($this->_items as $item)
		{
			if(isset($item['id']) && $item['id']==$input['merchantItemID']);
			return false;
		}

		$temp_r = array();
		foreach($input as $key => $value)
		{
			if($key=='merchantItemID')
			{
				$temp_r['id'] = $value;
			} else {
				$temp_r[$key] = $value;
			}
		}
		$this->_items[] = $temp_r;
		return true;
	}

	/*
	 * 
	 */
	function dumpToSessionArr() {
		if(!empty($this->_items))
		{
			$sessR = serialize($this->_items);
			$this->Session->write('Google', $sessR);
		}
		return true;
	}

	/*
	 * 
	 */
	function _restoreFromSessionArr() {
		if(empty($this->_items))
		{
			$sessR = $this->Session->read('Google');
			
			if($sessR==false) {
				return false;
			} else {
				$this->_items = null;
				$this->_items = unserialize($sessR);

				return true;
			}
		}
	}

	/*
	 * 
	 */
	function getCartItems() {
		return $this->_items;
	}

	/*
	 * 
	 */
	function getCartPreTaxTotal() {
		$total = 0;
		
		foreach($this->_items as $item)
		{
			$total += $item['price'];
		}
		
		return $total;
	}

	/*
	 * Do all the API processing funcitons right here
	 * This way cake controller action should just call $google_checkout->ProcessXmlData($HTTP_RAW_POST_DATA)
	 */
	function ProcessXmlData($xml_data) {
		$Gresponse = new GoogleResponse($this->_merchantID, $this->_merchantKey);
		$Grequest = new GoogleRequest($this->_merchantID, $this->_merchantKey, $this->_gserve, $this->_currency);

		//Setup the log file
		$Gresponse->SetLogFiles(RESPONSE_HANDLER_ERROR_LOG_FILE, RESPONSE_HANDLER_LOG_FILE, L_ALL);
		
		// Retrieve the XML sent in the HTTP POST request to the ResponseHandler
		$xml_response = isset($xml_data) ? $xml_data : file_get_contents("php://input");
		if (get_magic_quotes_gpc()) {
			$xml_response = stripslashes($xml_response);
		}
		list($root, $data) = $Gresponse->GetParsedXML($xml_response);
		$Gresponse->SetMerchantAuthentication($merchant_id, $merchant_key);
		
		$status = $Gresponse->HttpAuthentication();
		if(! $status) {
			die('authentication failed');
		}

	    /*
		//requests, errors and problem solving
	     *     <request-received>
	     *     <error>
	     *     <diagnosis>
		//Go to google and pay for it.
	     *     <checkout-redirect> //a redirect
		//Notifications....
	     *     <merchant-calculation-callback>
	     *     <new-order-notification>
	     *     <order-state-change-notification>
	     *     <charge-amount-notification>
	     *     <chargeback-amount-notification>
	     *     <refund-amount-notification>
	     *     <risk-information-notification>
	     */
		switch ($message_recognizer) {
			// <checkout-redirect> received //handled in a predictable manner, so I can integrate this one...
			case "checkout-redirect":
				$this->_ProcessCheckoutRedirect($dom_response_obj);
				break;
			case "request-received":
			case "error":
			case "diagnosis":
			case "merchant-calculation-callback": {

				// Create the results and send it
				$merchant_calc = new GoogleMerchantCalculations($currency);
				
				// Loop through the list of address ids from the callback
				$addresses = get_arr_result($data[$root]['calculate']['addresses']['anonymous-address']);
				foreach($addresses as $curr_address)
				{
					$curr_id = $curr_address['id'];
					$country = $curr_address['country-code']['VALUE'];
					$city = $curr_address['city']['VALUE'];
					$region = $curr_address['region']['VALUE'];
					$postal_code = $curr_address['postal-code']['VALUE'];
					
					// Loop through each shipping method if merchant-calculated shipping
					// support is to be provided
					if(isset($data[$root]['calculate']['shipping']))
					{
						
						$shipping = get_arr_result($data[$root]['calculate']['shipping']['method']);
						
						foreach($shipping as $curr_ship)
						{
							$name = $curr_ship['name'];
						
							//Compute the price for this shipping method and address id
							$price = 12; // Modify this to get the actual price
							$shippable = "true"; // Modify this as required
							$merchant_result = new GoogleResult($curr_id);
							$merchant_result->SetShippingDetails($name, $price, $shippable);
							
							if($data[$root]['calculate']['tax']['VALUE'] == "true") {
								//Compute tax for this address id and shipping type
								$amount = 15; // Modify this to the actual tax value
								$merchant_result->SetTaxDetails($amount);
							}
							
							if(isset($data[$root]['calculate']['merchant-code-strings']['merchant-code-string']))
							{
								$codes = get_arr_result($data[$root]['calculate']['merchant-code-strings']['merchant-code-string']);
	
								foreach($codes as $curr_code)
								{
									//Update this data as required to set whether the coupon is valid, the code and the amount
									$coupons = new GoogleCoupons("true", $curr_code['code'], 5, "test2");
									$merchant_result->AddCoupons($coupons);
								}
							}
	
							$merchant_calc->AddResult($merchant_result);
						}

					} else {

						$merchant_result = new GoogleResult($curr_id);
						
						if($data[$root]['calculate']['tax']['VALUE'] == "true")
						{
							//Compute tax for this address id and shipping type
							$amount = 15; // Modify this to the actual tax value
							$merchant_result->SetTaxDetails($amount);
						}
						
						$codes = get_arr_result($data[$root]['calculate']['merchant-code-strings']['merchant-code-string']);
						
						foreach($codes as $curr_code)
						{
							//Update this data as required to set whether the coupon is valid, the code and the amount
							$coupons = new GoogleCoupons("true", $curr_code['code'], 5, "test2");
							$merchant_result->AddCoupons($coupons);
						}
						
						$merchant_calc->AddResult($merchant_result);
	
					}
				}
				$Gresponse->ProcessMerchantCalculations($merchant_calc);
				break;
			}
			case "new-order-notification": {
				$Gresponse->SendAck();
				break;
			}
			case "order-state-change-notification": {
				$Gresponse->SendAck();
				$new_financial_state = $data[$root]['new-financial-order-state']['VALUE'];
				$new_fulfillment_order = $data[$root]['new-fulfillment-order-state']['VALUE'];
				
				switch($new_financial_state) {
					case 'REVIEWING': {
						break;
					}
					case 'CHARGEABLE': {
						$Grequest->SendProcessOrder($data[$root]['google-order-number']['VALUE']);
						$Grequest->SendChargeOrder($data[$root]['google-order-number']['VALUE'],'');
					break;
					}
					case 'CHARGING': {
						break;
					}
					case 'CHARGED': {
						break;
					}
					case 'PAYMENT_DECLINED': {
						break;
					}
					case 'CANCELLED': {
						break;
					}
					case 'CANCELLED_BY_GOOGLE': {
						//$Grequest->SendBuyerMessage($data[$root]['google-order-number']['VALUE'],
						//    "Sorry, your order is cancelled by Google", true);
						break;
					}
					default:
					break;
				}
			}
			case "charge-amount-notification": {
				//$Grequest->SendDeliverOrder($data[$root]['google-order-number']['VALUE'],
				//    <carrier>, <tracking-number>, <send-email>);
				//$Grequest->SendArchiveOrder($data[$root]['google-order-number']['VALUE'] );
				$Gresponse->SendAck();
				break;
			}
			case "chargeback-amount-notification":
				$Gresponse->SendAck();
				break;
			case "refund-amount-notification":
				$Gresponse->SendAck();
				break;
			case "risk-information-notification":
				$Gresponse->SendAck();
				break;
	        default:
				$Gresponse->SendBadRequestStatus("Invalid or not supported Message");
				break;
	    }
	}

	function _ProcessCheckoutRedirect($dom_response_obj) {
	    // Identify the URL to which the customer should be redirected
	    $dom_data_root = $dom_response_obj->document_element();
	    $redirect_url_list = $dom_data_root->get_elements_by_tagname("redirect-url");
	    $redirect_url = $redirect_url_list[0]->get_content();
	    // Redirect the customer to the URL
		$this->controller->redirect($redirect_url);
		exit();
	}
	
	function _SendNotificationAcknowledgment() {
		$acknowledgement = "<notification-acknowledgment xmlns=\"".$GLOBALS["schema_url"] . "\"/>";
		return $acknowledgment;
		
		// Log <notification-acknowledgment> was at least called from cake controller
		LogMessage($GLOBALS["logfile"], $acknowledgment);
	}
	
	/**
	 * The CreateArchiveOrder function is a wrapper function that calls the
	 * ChangeOrderState function. The ChangeOrderState function, in turn,
	 * creates an <archive-order> command for the specified order, which is
	 * identified by its Google Checkout order number ($google_order_number). The 
	 * <archive-order> command moves an order from the Inbox in the Google Checkout
	 * Merchant Center to the Archive folder.
	 *
	 * @param   $google_order_number    A number, assigned by Google Checkout, 
	 *                                      that uniquely identifies an order.
	 * @return  <archive-order> XML
	 */
	function CreateArchiveOrder($google_order_number) {
		return ChangeOrderState($google_order_number, "archive");
	}

	/**
	 * The CreateCancelOrder function is a wrapper function that calls the
	 * ChangeOrderState function. The ChangeOrderState function, in turn,
	 * creates a <cancel-order> command for the specified order, which is
	 * identified by its Google Checkout order number ($google_order_number). The 
	 * <cancel-order> command instructs Google Checkout to cancel an order.
	 *
	 * @param   $google_order_number    A number, assigned by Google Checkout, 
	 *                                      that uniquely identifies an order.
	 * @param   $reason                 The reason an order is being canceled
	 * @param   $comment                A comment pertaining to a canceled order
	 * @return  <cancel-order> XML
	 */
	function CreateCancelOrder($google_order_number, $reason, $comment="") {
		return ChangeOrderState($google_order_number, "cancel", $reason, 0, $comment);
	}

	/**
	 * The CreateChargeOrder function is a wrapper function that calls the
	 * ChangeOrderState function. The ChangeOrderState function, in turn,
	 * creates a <charge-order> command for the specified order, which is
	 * identified by its Google Checkout order number ($google_order_number). The
	 * <charge-order> command prompts Google Checkout to charge the customer for an 
	 * order and to change the order's financial order state to "CHARGING".
	 *
	 * @param   $google_order_number    A number, assigned by Google Checkout, 
	 *                                      that uniquely identifies an order.
	 * @param   $amount                 The amount that Google Checkout should 
	 *                                      charge the customer
	 * @return  <charge-order> XML
	 */
	function CreateChargeOrder($google_order_number, $amount="") {
	    return ChangeOrderState($google_order_number, "charge", 0, $amount);
	}

	/**
	 * The CreateProcessOrder function is a wrapper function that calls the
	 * ChangeOrderState function. The ChangeOrderState function, in turn,
	 * creates a <process-order> command for the specified order, which is
	 * identified by its Google Checkout order number ($google_order_number). The
	 * <process-order> command changes the order's fulfillment order state 
	 * to "PROCESSING".
	 *
	 * @param   $google_order_number    A number, assigned by Google Checkout, 
	 *                                      that uniquely identifies an order.
	 * @return  <process-order> XML
	 */
	function CreateProcessOrder($google_order_number) {
	    return ChangeOrderState($google_order_number, "process");
	}

	/**
	 * The CreateRefundOrder function is a wrapper function that calls the
	 * ChangeOrderState function. The ChangeOrderState function, in turn,
	 * creates a <refund-order> command for the specified order, which is
	 * identified by its Google Checkout order number ($google_order_number). The 
	 * <refund-order> command instructs Google Checkout to issue a refund for an 
	 * order.
	 *
	 * @param   $google_order_number    A number, assigned by Google Checkout, 
	 *                                      that uniquely identifies an order.
	 * @param   $reason                 The reason an order is being refunded
	 * @param   $amount                 The amount that Google Checkout should 
	 *                                      refund to the customer.
	 * @param   $comment                A comment pertaining to a refunded order
	 * @return  <refund-order> XML
	 */
	function CreateRefundOrder($google_order_number, $reason, $amount="", $comment="") {
	    return ChangeOrderState($google_order_number, "refund", $reason, $amount, $comment);
	}

	/**
	 * The CreateUnarchiveOrder function is a wrapper function that calls the
	 * ChangeOrderState function. The ChangeOrderState function, in turn,
	 * creates an <unarchive-order> command for the specified order, which is
	 * identified by its Google Checkout order number ($google_order_number). The 
	 * <unarchive-order> command moves an order from the Archive folder in the
	 * Google Checkout Merchant Center to the Inbox.
	 *
	 * @param   $google_order_number    A number, assigned by Google Checkout, 
	 *                                      that uniquely identifies an order.
	 * @return  <unarchive-order> XML
	 */
	function CreateUnarchiveOrder($google_order_number) {
	    return ChangeOrderState($google_order_number, "unarchive");
	}
}